const path = require('path')
const fs = require('fs')
const inquirer = require('inquirer')
const archiver = require('archiver')
// 链接服务器的工具
const { NodeSSH } = require('node-ssh')
// node使用exec方法开启子进程
const { exec } = require('child_process')

const fileDirectory = 'C:/Users/Administrator/quickUpdate/'
const confiFileName = 'update.config.json'

let fileConfigList: any[] = []
getConfig()

/**
 * 获取更新项目配置信息
 */
function getConfig() {
  writeFileToDirectorySync(fileDirectory, confiFileName, JSON.stringify([]))
  let fileConfigListStr = fs.readFileSync(
    `${fileDirectory}${confiFileName}`,
    'utf8'
  )
  fileConfigList = fileConfigListStr ? JSON.parse(fileConfigListStr) : []
}

/**
 * 创建文件并检查当前路径是否存在
 */
function writeFileToDirectorySync(
  directoryPath: string,
  fileName: string,
  data: any
) {
  const filePath = `${directoryPath}/${fileName}`
  if (!fs.existsSync(directoryPath)) {
    fs.mkdirSync(directoryPath, { recursive: true })
  }
  if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, data)
}

exports.open = function openFile() {
  exec(`start "" "${fileDirectory}${confiFileName}"`)
}
/**
 * 输入服务器密码的交互
 *  host,
 *  port,
 *  username,
 *  fileName,
 *  buildPath,
 *  uploadDir,
 *  backupName,
 */
exports.config = async function () {
  try {
    let res = await inquirer.prompt([
      {
        type: 'input',
        name: 'name',
        message: '请输入配置名称：',
        default: '',
        validate: function (input: string) {
          if (!input) {
            return '配置名称不能为空'
          }
          return true
        },
      },
      {
        type: 'input',
        name: 'configMark',
        message: '请输入配置标识：',
        default: '',
        validate: function (input: string) {
          if (!input) {
            return '配置标识不能为空'
          }
          return true
        },
      },
      {
        type: 'input',
        name: 'buildRunPath',
        message: '请输入打包项目地址：',
        default: '',
      },
      {
        type: 'input',
        name: 'buildCmd',
        message: '请输入打包命令：',
        default: 'npm run build',
      },
      {
        type: 'input',
        name: 'host',
        message: '请输入服务器地址：',
        default: '',
        validate: function (input: string) {
          if (!input) {
            return '服务器地址不能为空'
          }
          return true
        },
      },
      {
        type: 'input',
        name: 'port',
        message: '请输入服务器端口号：',
        default: '80',
      },
      {
        type: 'input',
        name: 'username',
        message: '请输入服务器用户名：',
        default: 'root',
      },
      {
        type: 'input',
        name: 'password',
        message: '请输入服务器密码：',
        default: '',
        validate: function (input: string) {
          if (!input) {
            return '服务器密码不能为空'
          }
          return true
        },
      },
      {
        type: 'input',
        name: 'uploadDir',
        message: '请上传文件位置：',
        default: '',
        validate: function (input: string) {
          if (!input) {
            return '上传文件位置不能为空'
          }
          return true
        },
      },
      {
        type: 'input',
        name: 'fileName',
        message: '请输入上传文件名：',
        default: '',
        validate: function (input: string) {
          if (!input) {
            return '上传文件名不能为空'
          }
          return true
        },
      },
      {
        type: 'input',
        name: 'backupName',
        message: '请输入备份名：',
        default: '',
      },
    ])
    fileConfigList.push(res)
    fs.writeFileSync(
      path.join(fileDirectory, confiFileName),
      JSON.stringify(fileConfigList),
      'utf8'
    )
  } catch (err) {
    console.log(err, '输入错误')
  }
}
exports.upDate = async function () {
  const ssh = new NodeSSH()
  let startTime: null | Date = null
  let selectConfig: any = null
  let buildRunPath: any = null

  init()
  /**
   * 准备工作 配置好 服务器地址、端口号、用户名、文件名、本地打包文件路径、服务器文件路径
   * 1. 输入正确的密码连接服务器 连接线上ssh
   * 2. 把项目打包到本地指定目录下
   * 3. 压缩指定目录下的项目
   * 4. 上传压缩包到指定的服务器文件路径
  
   * 5. 备份之前项目,解压线上压缩包
   * 5. 删除本地打包
   */
  async function init() {
    let res: any = await selectProject()
    selectConfig = fileConfigList.find((item) => item.configMark == res['mark'])
    buildRunPath = selectConfig.buildRunPath || process.cwd()
    startTime = new Date()
    await connect()
    await buildFile()
    await compressFileZip()
    await uploadFileZip()

    if (selectConfig.backupName) {
      await backupFile()
    }

    updateFile()
    deletelocalFileZip()
  }

  /**
   * 通过ssh链接服务器
   */
  function connect() {
    return ssh
      .connect({
        host: selectConfig.host,
        username: selectConfig.username,
        port: selectConfig.port,
        password: selectConfig.password,
      })
      .then(() => {
        console.log('连接服务器成功')
      })
  }

  /**
   * 选择要更新的项目
   */
  function selectProject() {
    let choices = fileConfigList.map((item) => {
      return {
        value: `${item.configMark}`,
        name: item.name,
      }
    })
    return inquirer.prompt([
      {
        type: 'list',
        name: 'mark',
        message: '请选择要更新的项目',
        default: 'red',
        choices,
      },
    ])
  }

  /**
   * 把项目文件进行打包
   */
  function buildFile() {
    console.log('项目开始打包')

    return new Promise((resolve) => {
      // 在指定目录下运行命令
      exec(selectConfig.buildCmd, { cwd: buildRunPath }, (err: any) => {
        if (err) {
          console.log(err, '项目打包失败')
        } else {
          console.log('项目打包完成')
          resolve(true)
        }
      })
    })
  }

  /**
   * 把项目文件进行压缩
   */
  function compressFileZip() {
    return new Promise((resolve) => {
      console.log('源代码开始压缩')
      // 设置压缩级别
      let archive = archiver('zip', {
        zlib: {
          level: 8,
        },
      })

      // 创建文件输出流
      let output = fs.createWriteStream(
        `${buildRunPath}/${selectConfig.fileName}.zip`
      )
      // 存档警告
      archive.on('warning', function (err: any) {
        if (err.code === 'ENOENT') {
          console.warn('stat故障和其他非阻塞错误')
        } else {
          throw err
        }
      })
      // 存档出错
      archive.on('error', function (err: any) {
        console.log(err, '存档出错')
      })
      // 通过管道方法将输出流存档到文件
      archive.pipe(output)
      archive.directory(`${buildRunPath}/dist`, '/')
      archive.finalize()
      // 文件输出流结束
      output.on('close', function () {
        console.log(
          `总共${archive.pointer()} ${(archive.pointer() / 1024 / 1024).toFixed(
            2
          )} MB,完成源代码压缩`
        )
        resolve(true)
      })
    })
  }

  /**
   * 上传项目压缩包
   */
  function uploadFileZip() {
    console.log('开始上传zip压缩包')
    return ssh
      .putFile(
        `${buildRunPath}/${selectConfig.fileName}.zip`,
        `${selectConfig.uploadDir}/${selectConfig.fileName}.zip`
      )
      .then(() => {
        console.log('zip压缩包上传成功')
      })
      .catch((err: any) => {
        console.log(err, 'zip压缩包上传失败')
      })
  }

  /**
   * 备份项目
   */
  function backupFile() {
    /**
     * 在服务器上
     * test -e ${fileName} 检查文件是否存在
     * rm -rf ${backupName} 删除备份文件
     * cp ${fileName} ${backupName} 复制将上次提交项目备份
     */
    return ssh
      .execCommand(
        `test -e ${selectConfig.fileName} && rm -rf ${selectConfig.backupName} && cp -r ${selectConfig.fileName} ${selectConfig.backupName}`,
        {
          cwd: selectConfig.uploadDir,
        }
      )
      .then((res: any) => {
        if (res.code == 1) {
          if (res.stderr) {
            console.log('备份发生错误', res.stderr)
          } else {
            console.log('需要备份的文件不存在')
          }
        } else {
          console.log('完成备份,解压最新代码')
        }
      })
      .catch((err: any) => {
        console.log('备份发生未知链接错误', err)
      })
  }

  /**
   *  删除本地生成的压缩包
   */
  function deletelocalFileZip() {
    return fs.unlink(
      `${buildRunPath}/${selectConfig.fileName}.zip`,
      (err: any) => {
        if (err) {
          console.log(err, '删除本地生成的压缩包失败')
        } else {
          console.log('删除本地生成的压缩包成功')
        }
      }
    )
  }

  /**
   * 更新线上的项目文件
   */
  function updateFile() {
    console.log('开始删旧版本项目并解压压缩包、更新新版项目')
    /**
     * 在服务器上
     * rm -rf ${fileName} 删除原来的项目文件
     * unzip -o -d ${uploadDir}/${fileName} ${fileName}.zip 解压服务器上的压缩包
     * rm -f ${fileName}.zip 删除压缩包
     */
    return ssh
      .execCommand(
        `rm -rf ${selectConfig.fileName} && unzip -o -d ${selectConfig.uploadDir}/${selectConfig.fileName} ${selectConfig.fileName}.zip  && rm -f ${selectConfig.fileName}.zip && chmod -R u=rwx,g=rwx,o=rx ${selectConfig.fileName}`,
        {
          cwd: selectConfig.uploadDir,
        }
      )
      .then(() => {
        console.log(`项目包完成解压,${selectConfig.fileName}项目部署成功了!`)
        console.log(
          `项目更新时长${(new Date().getTime() - startTime!.getTime()) / 1000}s`
        )
        process.exit(0)
      })
      .catch((err: any) => {
        console.log(err, '解压出现错误')
      })
  }
}
